{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.6 使用滑动窗口进行数据采样"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在前面的章节中，我们将 token IDs 转换为连续的向量表示，也就是所谓的 token 编码，作为 LLM 的输入。然而，LLM 的一个缺点是自注意力机制（将在第 3 章详细介绍），不包含序列中的 token 位置或顺序信息。\n",
    "\n",
    "先前介绍的 embedding 层的生成方式中，相同的 token ID 总是被映射成相同的向量表示，不会在乎 token ID 在输入序列中的位置，如图 2.17 所示。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**图 2.17 编码层将 token ID 转换为相同的向量表示，不管其在输入序列中的位置如何。例如，token ID 5 ，无论是在 token ID 输入向量的第一还是第三个位置，都将产生相同的编码向量。**\n",
    "\n",
    "![](../img/fig-2-17.jpg)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在原则上，具有确定性的、与位置无关的编码对于可重现性是有益的。然而，由于LLM的自注意力机制本身也不关注位置，因此将额外的位置信息注入到LLM中是有帮助的。\n",
    "\n",
    "为了实现这一点，有两种常用的位置编码方式：相对位置编码和绝对位置编码。\n",
    "\n",
    "绝对位置编码与序列中的特定位置相关联。对于输入序列中的每个位置，都会添加一个唯一的位置编码到 token 中，来表示其确切位置。例如，第一个 token 将具有特定的位置编码，第二个 token 将具有另一个不同的位置编码，依此类推，如图2.18所示。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**图2.18 位置编码被添加到 token 中，以创建 LLM 的输入。位置向量具有与原始 token 相同的维度。出于简化考虑，token 编码显示为值为1。**\n",
    "\n",
    "![](../img/fig-2-18.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "相对位置编码不是专注于 token 的绝对位置，而是侧重于 token 之间的相对位置或距离。这意味着模型学习的是 “彼此之间有多远” 而不是 “在哪个确切位置”的关系。这样做的好处是，即使模型在训练过程中没有看到这样的长度，它也可以更好地推广到不同长度的序列。\n",
    "\n",
    "两种类型的位置编码旨在增强 LLM 理解 token 顺序和关系的能力，确保更准确和更具上下文意识的预测。它们的选择通常取决于具体的应用程序和正在处理的数据的性质。\n",
    "\n",
    "OpenAI 的 GPT 模型使用绝对位置编码，在训练过程中进行了优化，而不是像原始 Transformer 模型中的位置编码那样固定或预定义。这个优化过程是模型训练本身的一部分，我们将在本书的后面部分实现。现在，让我们创建初始的位置编码，为即将到来的章节创建 LLM 输入。\n",
    "\n",
    "在本章中，我们之前专注于非常小的编码大小，以说明为目的。现在，我们考虑更现实和有用的编码大小，并将输入 token 编码为 256 维向量表示。这比原始的GPT-3 模型使用的要小（在 GPT-3 中，编码大小为 12,288 维），但对于实验来说仍然是合理的。此外，我们假设 token ID 是由我们之前实现的 BPE 标记器创建的，其词汇量大小为 50,257：\n",
    "> output_dim = 256\n",
    "\n",
    "> vocab_size = 50257\n",
    "\n",
    "> token_embedding_layer = torch.nn.Embedding(vocab_size, output_dim)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用上面的 token_embedding_layer ，如果我们从 dataloader 中采样数据，我们将每个批次中的每个 token 编码成一个256维的向量中。如果我们的批次大小为8，每个批次有四个 token ，则结果将是一个8 x 4 x 256的张量。\n",
    "\n",
    "首先，让我们实例化第2.6节 “使用滑动窗口进行数据采样” 的 dataloader：\n",
    ">max_length = 4\n",
    "\n",
    ">dataloader = create_dataloader_v1(\n",
    ">    raw_text, batch_size=8, max_length=max_length, stride=max_len)\n",
    "\n",
    ">data_iter = iter(dataloader)\n",
    "\n",
    ">inputs, targets = next(data_iter)\n",
    "\n",
    ">print(\"Token IDs:\\n\", inputs)\n",
    "\n",
    ">print(\"\\nInputs shape:\\n\", inputs.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "前面的代码打印如下输出:\n",
    ">Token IDs:\n",
    "\n",
    ">tensor([[   40,   367,  2885,  1464],\\\n",
    "        [ 1807,  3619,   402,   271],\\\n",
    "        [10899,  2138,   257,  7026],\\\n",
    "        [15632,   438,  2016,   257],\\\n",
    "        [  922,  5891,  1576,   438],\\\n",
    "        [  568,   340,   373,   645],\\\n",
    "        [ 1049,  5975,   284,   502],\\\n",
    "        [  284,  3285,   326,    11]])\n",
    "        \n",
    ">Inputs shape:\n",
    " >torch.Size([8, 4])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "正如我们所看到的，token ID 张量是 8x4 维的，这意味着数据批次由 8 个文本样本组成，每个样本有 4 个 token。\n",
    "\n",
    "现在让我们使用 token_embedding_layer 将这些 token ID 嵌入为 256 维的向量：\n",
    "> token_embeddings = token_embedding_layer(inputs)\n",
    "> \n",
    "> print(token_embeddings.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "前面的代码打印如下输出:\n",
    "\n",
    ">torch.Size([8, 4, 256])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据 8x4x256 维的张量输出，我们可以看出每个 token ID 现在被嵌入为一个 256 维的向量。\n",
    "对于 GPT 模型的绝对嵌入方法，我们只需要创建另一个具有与 token_embedding_layer 相同维度的嵌入层：\n",
    "\n",
    ">context_length = max_length\n",
    "\n",
    ">pos_embedding_layer = torch.nn.Embedding(context_lengthe, output_dim)\n",
    "\n",
    ">pos_embeddings = pos_embedding_layer(torch.arange(context_length))\n",
    "\n",
    ">print(pos_embeddings.shape)\n",
    "\n",
    "如前面的代码示例所示，pos_embeddings 的输入通常是一个占位符向量 torch.arange(context_length)，其中包含一系列数字 0、1、...，一直到最大输入长度减 1。context_length 是一个变量，表示 LLM 支持的输入大小。在这里，我们选择它与输入文本的最大长度类似。在实践中，输入文本可以比支持的上下文长度长。这种情况下，我们必须截断文本。\n",
    "\n",
    "print 语句的输出如下：\n",
    "\n",
    ">torch.Size([4, 256])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "正如我们所见，位置嵌入张量由四个 256 维向量组成。我们现在可以直接将它们添加到标记嵌入中，PyTorch 将在每个 8 批次中的每个 4x256 维标记嵌入张量中添加 4x256 维的 pos_embeddings 张量：\n",
    ">input_embeddings = token_embeddings + pos_embeddings\n",
    "\n",
    ">print(input_embeddings.shape)\n",
    "\n",
    "print 输出如下：\n",
    "\n",
    ">torch.Size([8, 4, 256])\n",
    "\n",
    "我们创建的 input_embeddings，如图 2.19 所总结的，是嵌入的输入示例，现在可以由我们将在第 3 章开始实现的主要 LLM 模块进行处理。\n",
    "\n",
    "![](../img/fig-2-19.jpg)\n",
    "**图 2.19 作为输入处理流程的一部分，输入文本首先被分解为单个 token 。然后使用词汇表将这些标记转换为 token ID。将 token ID 转换为编码向量，然后添加相似大小的位置编码，生成用作主要 LLM 层的输入编码。**\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
